package request

import (
	"fmt"
	"io"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	sadefine "gitee.com/simplexyz/simplego/actor/define"
	sawork "gitee.com/simplexyz/simplego/actor/work"
	slog "gitee.com/simplexyz/simplego/log"
)

const (
	DefaultTimeout = 30 * time.Second
)

type ID = uint64
type Tag = uint64

type OnPrepareFunc func() (req *http.Request, err error)
type OnCompletedFunc func(err error, req *Request, ctx sadefine.Context)

type Request struct {
	err             error
	id              ID
	tag             Tag
	req             *http.Request
	resp            *http.Response
	cli             *http.Client
	onCompletedFunc OnCompletedFunc
}

func (r Request) ID() ID {
	return r.id
}

func (r Request) Tag() Tag {
	return r.tag
}

func (r Request) Request() *http.Request {
	return r.req
}

func (r Request) Response() *http.Response {
	return r.resp
}

func (r Request) GetResponseBodyData() (body []byte, err error) {
	return io.ReadAll(r.resp.Body)
}

var gRequestPool = sync.Pool{}

func createRequest(id ID, tag Tag, req *http.Request, cli *http.Client, onCompletedFunc OnCompletedFunc) *Request {
	r, ok := gRequestPool.Get().(*Request)
	if ok && r != nil {
		r.err = nil
		r.id = id
		r.tag = tag
		r.req = req
		r.resp = nil
		r.cli = cli
		r.onCompletedFunc = onCompletedFunc
	} else {
		r = &Request{
			id:              id,
			tag:             tag,
			req:             req,
			cli:             cli,
			onCompletedFunc: onCompletedFunc,
		}
	}
	return r
}

func destroyRequest(r *Request) {
	if r == nil {
		return
	}
	r.err = nil
	r.id = 0
	r.tag = 0
	r.req = nil
	r.resp = nil
	r.cli = nil
	r.onCompletedFunc = nil
	gRequestPool.Put(r)
}

type Manager struct {
	rootContext *sadefine.RootContext
	idGenerator ID
	requests    *sync.Map
	pid         *sadefine.PID
	logger      slog.ILogger
}

func NewManager(rootContext *sadefine.RootContext, pid *sadefine.PID, logger slog.ILogger) *Manager {
	return &Manager{
		rootContext: rootContext,
		idGenerator: 0,
		requests:    &sync.Map{},
		pid:         pid,
		logger:      logger,
	}
}

func (m *Manager) nextID() ID {
	return atomic.AddUint64(&m.idGenerator, 1)
}

func (m *Manager) Request(timeout time.Duration, tag Tag, onPrepare OnPrepareFunc, onCompletedFunc OnCompletedFunc) (id ID, err error) {
	if timeout <= 0 {
		timeout = DefaultTimeout
	}

	req, e := onPrepare()
	if e != nil {
		err = fmt.Errorf("onPrepare fail, %w", e)
		return
	}
	if req == nil {
		err = ErrOnPrepareResultMustNotNil
		return
	}

	if onCompletedFunc == nil {
		err = ErrOnCompletedMustNotNil
		return
	}

	cli := &http.Client{Timeout: timeout}

	id = m.nextID()

	r := createRequest(id, tag, req, cli, onCompletedFunc)

	m.requests.Store(r.id, r)

	go func() {
		r.resp, r.err = r.cli.Do(r.req)

		_ = sawork.Post(m.rootContext, m.pid, sawork.PostFunc(func(ctx sadefine.Context) {
			m.onComplete(id, ctx)
		}))
	}()

	return
}

func (m *Manager) onComplete(id ID, ctx sadefine.Context) {
	v, ok := m.requests.Load(id)
	if !ok {
		m.logger.Errorf("http request fail, request not found, id=%d", id)
		return
	}

	r, _ := v.(*Request)
	r.onCompletedFunc(r.err, r, ctx)

	if r.err == nil {
		if err := r.resp.Body.Close(); err != nil {
			m.logger.Errorf("close http response body fail, %w", err)
			return
		}
	}

	m.requests.Delete(r.id)

	destroyRequest(r)
}
